﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using SuperGo.SilverlightLib;
using SuperGo.SilverlightLib.History;
using System.Collections.Generic;

namespace SuperGo.Silverlight {
    public partial class Board {
        private GoGame _game;
		private int _size;
    	private bool _allowMoves = true;

		private const byte BOARD_RED = 242;
		private const byte BOARD_GREEN = 181;
		private const byte BOARD_BLUE = 100;

		public Stone[,] Stones { get; private set; }

    	public bool AllowMoves {
			get { 
				return LoggedInPlayerToMove() && _allowMoves;
			}
			set { _allowMoves = value; }
		}

		private bool LoggedInPlayerToMove() {
			return Page.LoggedInPlayerToMove();
		}

		public event RoutedEventHandler Click;

        public Board() {
			try {
				InitializeComponent();
				this.Loaded += new RoutedEventHandler(Board_Loaded);
			} catch (Exception ex) {
				HandleException(ex);
			}
		}

		void Board_Loaded(object sender, RoutedEventArgs e) {
			LayoutRoot.SizeChanged += new SizeChangedEventHandler(LayoutRoot_SizeChanged);
		}

		void LayoutRoot_SizeChanged(object sender, SizeChangedEventArgs e) {
			RedrawWoodGrain();
		}

		/// <summary>
		/// Draws the board.  The game param may be null if we're drawing a game that doesn't exist yet.
		/// </summary>
		public void DrawBoard(int size, GoGame game) {
			try {
				_game = game;
				_size = size;
				InitializeUI();
			} catch (Exception ex) {
				HandleException(ex);
			}
		}

        private void InitializeUI() {
			_woodGrainLines.Clear();
			LayoutRoot.Children.Clear();
			LayoutRoot.ColumnDefinitions.Clear();
			LayoutRoot.RowDefinitions.Clear();
			
			InitializeOuterGrid();

			// add wood grain
			AddWoodGrain();

			// add row and column labels
			AddOuterGridLabels();

			// initialize a new grid to house the board
			Grid boardGrid = new Grid();

			// create the rows and columns
			InitializeBoardGrid(boardGrid);

			// add the lines to the grid as a set of rectangles whose vertical ones look kind of like: "[]]]]]]"
			AddGridLines(boardGrid);

			// add star points
			AddStarPoints(boardGrid);

			// add the board grid to the outer grid
			LayoutRoot.Children.Add(boardGrid);

			// add empty stones onto the board (do this last so they are on top)
			AddStones();

			// set textbox colors
			SetColors(false);
        }

		private readonly List<Line> _woodGrainLines = new List<Line>();

		private void AddWoodGrain() {
			int numberOfLines = _size * 5;
			double actualWidth = LayoutRoot.ActualWidth;

			Random rand = new Random();

			for (int i = 0; i < numberOfLines; i++) {
				double X = GetWoodGrainX(actualWidth, numberOfLines, i);

				Line line = new Line{
                	X1 = X, 
					Y1 = 0, 
					X2 = X, 
					Y2 = LayoutRoot.ActualHeight, 
					Stroke = new SolidColorBrush(GetWoodGrainColor(rand)), 
					StrokeThickness = rand.Next(2, 8)
                };
				line.SetValue(Grid.RowSpanProperty, _size + 1);
				line.SetValue(Grid.ColumnSpanProperty, _size + 1);
				LayoutRoot.Children.Add(line);
				_woodGrainLines.Add(line);
			}
		}

		private static Color GetWoodGrainColor(Random rand) {
			byte R = BOARD_RED;
			byte G = BOARD_GREEN;
			byte B = BOARD_BLUE;

			byte delta = (byte)rand.Next(1, 10);
			// allow negative numbers
			delta += 3;

			R -= delta;
			G -= delta;
			B -= delta;

			return Color.FromArgb(255, R, G, B);
		}

		private static double GetWoodGrainX(double actualWidth, int numberOfLines, int currentLine) {
			if (actualWidth == 0 || numberOfLines == 0)
				return 0;
			return (actualWidth / numberOfLines) * currentLine;
		}
		
		private void RedrawWoodGrain() {
			int numberOfLines = _woodGrainLines.Count;
			double actualWidth = LayoutRoot.ActualWidth;

			int i = 0;
			foreach (Line line in _woodGrainLines) {
				double X = GetWoodGrainX(actualWidth, numberOfLines, i);
				
				line.Y2 = LayoutRoot.ActualHeight;
				line.X1 = X;
				line.X2 = X;

				i++;
			}
		}

		private void AddStarPoints(Panel boardGrid) {
			int[,] starIntersections = SilverlightLib.Board.GetStarPoints(_size);
			for (int i = 0; i < starIntersections.GetLength(0); i++) {
				AddStar(boardGrid, starIntersections[i, 0], starIntersections[i, 1]);
			}
		}

		private static void AddStar(Panel boardGrid, int x, int y) {
			Ellipse star = new Ellipse{
				Width = 6,
				Height = 6,
				Margin = new Thickness(0, 0, 1, 1),
				Fill = new SolidColorBrush(Colors.Black)
			};
			star.SetValue(Grid.RowProperty, y);
			star.SetValue(Grid.ColumnProperty, x);
			star.SetValue(Grid.RowSpanProperty, 2);
			star.SetValue(Grid.ColumnSpanProperty, 2);
			boardGrid.Children.Add(star);
		}

		private void InitializeOuterGrid() {
			for (int i = 0; i < _size + 1; i++) {
				LayoutRoot.RowDefinitions.Add(new RowDefinition());
				LayoutRoot.ColumnDefinitions.Add(new ColumnDefinition());
			}
		}

		private void AddGridLines(Panel boardGrid) {
			for (int i = 1; i < _size; i++) {
				Rectangle rect = new Rectangle();
				rect.SetValue(Grid.RowProperty, 1);
				rect.SetValue(Grid.ColumnProperty, 1);
				rect.SetValue(Grid.RowSpanProperty, _size - 1);
				rect.SetValue(Grid.ColumnSpanProperty, i);
				rect.Style = GridlinesStyle;

				boardGrid.Children.Add(rect);

				rect = new Rectangle();
				rect.SetValue(Grid.RowProperty, 1);
				rect.SetValue(Grid.ColumnProperty, 1);
				rect.SetValue(Grid.RowSpanProperty, i);
				rect.SetValue(Grid.ColumnSpanProperty, _size - 1);
				rect.Style = GridlinesStyle;

				boardGrid.Children.Add(rect);
			}
		}

		private void AddStones() {
			Stones = new Stone[_size, _size];
			for (int x = 0; x < _size; x++) {
				for (int y = 0; y < _size; y++) {
					Stone stone = new Stone(y, x, _size);
					stone.SetValue(Grid.RowProperty, y + 1);
					stone.SetValue(Grid.ColumnProperty, x + 1);
					
					stone.MouseLeftButtonUp += stone_MouseLeftButtonUp;
					stone.MouseEnter += new MouseEventHandler(stone_MouseEnter);
					stone.MouseLeave += new MouseEventHandler(stone_MouseLeave);

					LayoutRoot.Children.Add(stone);

					Stones[x, y] = stone;
				}
			}
		}

		private void InitializeBoardGrid(Grid boardGrid) {
			boardGrid.SetValue(Grid.RowProperty, 1);
			boardGrid.SetValue(Grid.ColumnProperty, 1);
			boardGrid.SetValue(Grid.RowSpanProperty, _size);
			boardGrid.SetValue(Grid.ColumnSpanProperty, _size);

			ColumnDefinitionCollection columnDefinitionCollection = boardGrid.ColumnDefinitions;
			columnDefinitionCollection.Add(new ColumnDefinition { Width = new GridLength(.5, GridUnitType.Star) });

			RowDefinitionCollection rowDefinitionCollection = boardGrid.RowDefinitions;
			rowDefinitionCollection.Add(new RowDefinition { Height = new GridLength(.5, GridUnitType.Star) });

			for (int i = 0; i < _size - 1; i++) {
				columnDefinitionCollection.Add(new ColumnDefinition());
				rowDefinitionCollection.Add(new RowDefinition());
			}

			columnDefinitionCollection.Add(new ColumnDefinition { Width = new GridLength(.5, GridUnitType.Star) });
			rowDefinitionCollection.Add(new RowDefinition { Height = new GridLength(.5, GridUnitType.Star) });
		}

		private void AddOuterGridLabels() {
			for (int i = 0; i < _size; i++) {
				TextBlock textBlock = new TextBlock();
				textBlock.SetValue(Grid.RowProperty, 0);
				textBlock.SetValue(Grid.ColumnProperty, i + 1);
				textBlock.Style = GridLineIdentifiersStyle;
				textBlock.Text = Intersection.GetLetter(i).ToString();
				LayoutRoot.Children.Add(textBlock);

				textBlock = new TextBlock();
				textBlock.SetValue(Grid.RowProperty, i + 1);
				textBlock.SetValue(Grid.ColumnProperty, 0);
				textBlock.Style = GridLineIdentifiersStyle;
				textBlock.Text = (i + 1).ToString();
				LayoutRoot.Children.Add(textBlock);

			}
		}

		protected void stone_MouseLeave(object sender, MouseEventArgs e) {
			try {
				if (_game == null)
					return;

				Stone stone = sender as Stone;
				if (stone == null || !AllowMoves)
					return;

				stone.Redraw(_game);

				//InvalidMoveReason reason;
				//if (_game.IsValidMove(stone.Column, stone.Row, out reason)) {
				//    RedrawStone(stone.Column, stone.Row);
				//} else {
				//    if (reason == InvalidMoveReason.Ko || reason == InvalidMoveReason.Suicide) {
				//        stone.SetState(StoneState.Empty, StoneStatus.Normal);
				//    }
				//}
			} catch (Exception ex) {
				HandleException(ex);
			}
		}

		private void RedrawStone(HistoryEvent historyEvent, int x, int y) {
			GameState gameState = GameHistory.GetGameState(historyEvent);
			StoneState stoneState = historyEvent.Board[x, y].State;
			Stone stone = Stones[x, y];
			stone.Redraw(_game, gameState, stoneState, StoneStatus.Normal);
		}

    	public void RedrawStone(int x, int y) {
			Stones[x, y].Redraw(_game, StoneStatus.Normal);
		}

		public void RedrawStone(int x, int y, StoneState stoneState) {
			Stones[x, y].Redraw(_game, _game.GameState, stoneState, StoneStatus.Normal);
		}

    	protected void stone_MouseEnter(object sender, MouseEventArgs e) {
			try {
				if (_game == null || !AllowMoves)
					return;

				Stone stone = sender as Stone;
				
				if (stone == null)
					return;

				GameState gameState = _game.GameState;

				StoneState potentialStoneState = GetStoneState(gameState);

				InvalidMoveReason reason;
				if (_game.IsValidMove(stone.X, stone.Y, out reason)) {
					stone.Redraw(_game, potentialStoneState, StoneStatus.Hover);
				} else {
					if (reason == InvalidMoveReason.Ko) {
						stone.SetState(potentialStoneState, StoneStatus.Ko);
					}
					if (reason == InvalidMoveReason.Suicide) {
						stone.SetState(potentialStoneState, StoneStatus.Suicide);
					}
				}

			} catch (Exception ex) {
				HandleException(ex);
			}
		}

		protected void stone_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) {
			try {
				Stone stone = sender as Stone;
				if (stone == null || !AllowMoves)
					return;

				PlaceStone(stone);

				if (Click != null)
					Click(this, null);
			} catch (Exception ex) {
				HandleException(ex);
			}
		}

		private static StoneState GetStoneState(GameState gameState) {
			if (gameState == GameState.BlackToMove) {
				return StoneState.Black;
			}
			return gameState == GameState.WhiteToMove ? StoneState.White : StoneState.Empty;
		}

    	public Stone GetLastMove() {
			if (_game.LastMove == null)
				return null;
			int x = _game.LastMove.X;
			int y = _game.LastMove.Y;
			return Stones[x, y];
		}
		
		public void DrawBoard(HistoryEvent historyEvent, bool inHistory) {
			AllowMoves = !inHistory;
			SilverlightLib.Board board = historyEvent.Board;

			for (int y = 0; y < board.Size; y++) {
				for (int x = 0; x < board.Size; x++) {
					RedrawStone(historyEvent, x, y);
				}
			}

			SetColors(inHistory);
		}

		private void SetColors(bool inHistory) {
			try {
				if (!inHistory) {
					LayoutRoot.Background = new SolidColorBrush(Color.FromArgb(255, BOARD_RED, BOARD_GREEN, BOARD_BLUE));
					SetGridlineIdentifiersColor(Color.FromArgb(255, 165, 42, 42));
				} else {
					LayoutRoot.Background = new SolidColorBrush(Color.FromArgb(255, 185, 185, 185));
					SetGridlineIdentifiersColor(Color.FromArgb(255, 75, 75, 75));
				}
			} catch (Exception ex) {
				HandleException(ex);
			}
		}

		private void SetGridlineIdentifiersColor(Color color) {
			SolidColorBrush solidColorBrush = new SolidColorBrush(color);
			foreach (FrameworkElement element in LayoutRoot.Children) {
				TextBlock textBlock = element as TextBlock;
				if (textBlock != null) {
					textBlock.SetValue(TextBlock.ForegroundProperty, solidColorBrush);
				}
			}
		}

		public void PlaceStone(int x, int y) {
			PlaceStone(Stones[x, y]);
		}
		
		public void PlaceStone(Stone newMove) {
			if (_game == null)
				return;

			try {
				if (_game.IsValidMove(newMove.X, newMove.Y)) {
					try {
						Stone lastMove = GetLastMove();
						_game.Play(newMove.X, newMove.Y);
						if (lastMove != null)
							lastMove.Redraw(_game);
						newMove.Redraw(_game);
					} catch (GoException) {
					}
				}
			} catch (Exception ex) {
				HandleException(ex);
			}
		}

		public void Redraw() {
			try {
				if (_game == null)
					throw new InvalidOperationException("_game can not be null during a call to Redraw().");

				for (int x = 0; x < _size; x++) {
					for (int y = 0; y < _size; y++) {
						RedrawStone(x, y);
					}
				}
			} catch (Exception ex) {
				HandleException(ex);
			}
		}
	}
}
